-module(test).
-compile(export_all).

-behavior(gen_server).
-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).



start() ->
	gen_server:start({local,?MODULE},?MODULE,[],[]).
	
stop() ->
	gen_server:call(?MODULE,stop).
	
sync() ->
	os:cmd("p4 sync //r.game/...").
	
%% config
		
print_edge(X,Y) ->
	io:format("~p --> ~p~n",[X,Y]).
	
%% plan graph
add_vertex(_,[]) -> ok;
add_vertex(D,[X|T]) ->
	digraph:add_vertex(D,X),
	add_vertex(D,T);
add_vertex(D,X) ->
	digraph:add_vertex(D,X).
add_edge(_,_,[]) -> ok;
add_edge(_,[],_) -> ok;
add_edge(D,X,[Y|T2]) ->
	add_edge(D,X,Y),
	add_edge(D,X,T2);	
add_edge(D,[X|T],Y) ->
	print_edge(X,Y),
	digraph:add_edge(D,X,Y),
	add_edge(D,T,Y);
add_edge(D,X,Y) ->
	print_edge(X,Y),
	digraph:add_edge(D,X,Y).
			
build_graph() ->
	D = digraph:new(),	

	{Edges,Vertices} = jobs:edges_and_vertices(),
	add_vertex( D, Vertices ),
		lists:foreach(
		fun({A,B}) -> add_edge(D,A,B) end,
		Edges
	),
	D.


has_no_pred(S,X) ->
	%% lists:foreach(fun(V)->say("~p/~p~n",[X,V]) end,digraph_utils:reachable([X],S)),
	digraph_utils:reachable([X],S) =:= [X].
	

del_vertices(D,[H|T]) ->
	digraph:del_vertex(D,H),
	del_vertices(D,T);
	
del_vertices(_,[]) -> ok.
	
plan(D,X) -> 
 	%% X를 만들기 위해 필요한 것들만 subset으로 취한다
	del_vertices(D,digraph:vertices(D) -- digraph_utils:reachable([X],D)),
	step(D,[]).

do_proxy(Sender,Job) -> 
	io:format("~p:Job ~p begins~n", [self(),Job]),
	%% guarded execution
	try 
		jobs:do(Job),
		io:format("~p:Job ~p ends~n", [self(),Job]),
		Sender!ok
	catch
		_ ->
			io:format("~p:Job ~p failed~n", [self(),Job]),
			Sender!failed
	end.
	
work(Coordinator,V) ->
	S = self(),
	%% 별도의 process에서 작업을 시작한다. 이 process는 coordinator와의 교신을 담당한다.
	spawn(?MODULE,do_proxy,[S,V]),
	receive
		ok -> Coordinator ! V;
		{ping,From} -> From ! pong
	end.

wait_for_job(D,WS) ->
	say("wait for job"),
	io:format("~p jobs working~n",[length(WS)]),
	receive
		V -> 
			say("Job " ++ atom_to_list(V) ++ " end"),
			jobs_done(D,WS,[V])
	after 
		1000 ->
			say("pinging jobs"),
			wait_for_job(D,WS)
	end.
	
jobs_done(D,WS,JobsDone) ->
	say("Jobs done"),
	del_vertices(D,JobsDone),
	case digraph:vertices(D) =:= [] of
		true -> ok;
		false -> step(D,WS -- JobsDone)
	end.

next_jobs(D,WS) ->
	NWS = lists:filter(
		fun(V) -> has_no_pred(D,V) end, 
		digraph:vertices(D)),
	NWS--WS.
			
step(D,WS) ->
	invoke(D,WS,next_jobs(D,WS)).	

%% 분산 처리 관리자
alloc_node(WS,_V) -> 
	case length(WS) < 10 of 
		true -> node(); 
		_ -> nil 
	end.
	
%% 작업을 분산 노드에 시작한다.
spawn_work(nil,_) -> nil;
spawn_work(Node,V) ->
	S = self(),
	case spawn(Node,?MODULE,work,[S,V]) of
		nil -> nil;
		_Pid -> V 
	end.
	
invoke(D,WS,NWS) ->
	Activated = lists:filter(
		fun(V) -> V =/= nil end,
		lists:map(
			fun(V) -> spawn_work(alloc_node(WS,V),V) end,
		NWS)),
	wait_for_job(D,WS++Activated).
	
test() -> 
	plan(
		build_graph(),
		shipping_build
	).

init(_Arg) -> 
	say("hello"),
	{ok,[]}.
	
terminate(_Reason,_S) -> ok.
code_change(_OldVsn,State,_Extra) -> {ok,State}.
handle_call(stop,_From,State) -> {stop,normal,stopped,State};
handle_call(_Request,_From,State) -> {reply,ok,State}.
handle_cast(_Msg,_S) -> {noreply,ok}.
handle_info(_Info,_S) -> {noreply,ok}.

%% Some helper methods.

say(Format) ->
  say(Format, []).
say(Format, Data) ->
  io:format("~p:~p: ~s~n", [?MODULE, self(), io_lib:format(Format, Data)]).
